package TopK;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import Basics.Atom;
import Basics.Body;
import Basics.KeyMap;
import Basics.Provenance;

public class EquationTopK 
{	
	Set<Atom> treesUpdatedLastIter = new HashSet<Atom>();
	
	int k;
	
	boolean factsUpdated = false;
	
	boolean online = false;
	

	public EquationTopK () {}
	
	
	
	public EquationTopK (int ik)
	{
		this.k = ik;
	}
	
	
	
	public EquationTopK (int ik, boolean on)
	{
		this.k = ik;
		this.online = on;
	}
	
	

	public Set<Atom> getTreesUpdatedLastIter() 
	{
		return treesUpdatedLastIter;
	}


	public void setTreesUpdatedLastIter(Set<Atom> treesUpdatedLastIter) 
	{
		this.treesUpdatedLastIter = treesUpdatedLastIter;
	}



	public void addToTreesUpdatedLastIter(Atom atom) 
	{
		this.treesUpdatedLastIter.add(atom);
	}



	/*************************************************************************************************************/
	/** Title: TopK																					
	/** Description: Calculate top-k deriv. trees for each atom in the prov.									
	/*************************************************************************************************************/
	
	public void TopK ()
	{
		Set<Atom> previous;
		//MarkAtomsTreesChangedLastIteration();
		if (false == this.factsUpdated)
		{
			UpdateFactAtoms();
		}
		
		while (false == this.treesUpdatedLastIter.isEmpty())
		{
			previous = new HashSet<Atom> (this.treesUpdatedLastIter);
			TopKIteration();
			SetAtomsToNotUpdatedLastIteration(previous);
		}
		
		for (Atom atom : KeyMap.getInstance().Values()) {
			if (atom.toString().equals("dealsWith1(Canada,Andorra)") && atom.getTrees() != null) {
				System.out.println("");
			}
		}
	}
	
	
	/*************************************************************************************************************/
	/** Title: SetAtomsToNotUpdatedLastIteration																					
	/** Description: One iteration of the top-k algorithm on the prov.									
	/*************************************************************************************************************/
	
	public void SetAtomsToNotUpdatedLastIteration (Set<Atom> previous) 
	{
		for (Atom atom : previous) 
		{
			atom.setTreesChangedLastIteration(false);
		}
		
		this.treesUpdatedLastIter.removeAll(previous);
	}
	
	

	/*************************************************************************************************************/
	/** Title: UpdateFactAtoms																					
	/** Description: Makes the trees for the fact atoms int the prov. 									
	/*************************************************************************************************************/
	
	public void UpdateFactAtoms () 
	{
		for (Atom atom : KeyMap.getInstance().Values()) 
		{
			if (true == atom.isFact() && false == atom.isTopKUpdated() ) 
			{
				SetTreeForFact(atom);
				this.treesUpdatedLastIter.add(atom);
			}
		}
		
		this.factsUpdated = true;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: TopKIteration																					
	/** Description: One iteration of the top-k algorithm on the prov.									
	/*************************************************************************************************************/
	
	public void TopKIteration () 
	{
		for (Atom key : KeyMap.getInstance().Values()) 
		{
			FindTopKTrees(key);
		}
	}
	
	
	/*************************************************************************************************************/
	/** Title: FindTopKTrees																					
	/** Description: Given an atom, goes through all derivations of this atom to find the top k trees 									
	/*************************************************************************************************************/
	
	public void FindTopKTrees(Atom key)
	{
		List<DerivationTree> topKTrees = new Vector<DerivationTree>(); 
		for (Body body : Provenance.getInstance().Get(key)) 
		{
			if (true == body.AllAtomsHaveTopK() && true == body.HasAtomInTreesUpdatedLastIter()) 
			{
				GetTopKTreesForBody(key, body, topKTrees);
			}
		}
		
		SiftTopKTrees(key, topKTrees);
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: SameWeightTrees																					
	/** Description: Checks that all trees are the same weight 									
	/*************************************************************************************************************/
	
	public boolean SameWeightTrees(List<DerivationTree> curTrees, List<DerivationTree> prevTrees)
	{
		boolean retVal = true;
		if (null != prevTrees) 
		{
			if (prevTrees.size() == this.k) 
			{
				for (int i = 0; i < this.k; i++) 
				{
					if (curTrees.get(i).getWeight() != prevTrees.get(i).getWeight())
					{
						retVal = false;
						break;
					}
				}
			}
			
			else
			{
				retVal = false;
			}
		}
		
		else
		{
			retVal = false;
		}
		
		return retVal;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: HandleANDNode																					
	/** Description: Handles AND type node in the circuit														
	/*************************************************************************************************************/
	
	public void GetTopKTreesForBody (Atom key, Body b, List<DerivationTree> topKTrees)
	{
		Vector<List<DerivationTree>> arr = new Vector<List<DerivationTree>>();
		GetChildrenTreeVectors(arr, b);
		ComparissonTree.CompareNode root = new ComparissonTree.CompareNode();
		root.setIndices(new int[arr.size()]);

		ComparissonTree compareTree = new ComparissonTree (root);
		ComparissonTree.CompareNode chosenNode;

		for (int i = 0; i < this.k; i++) 
		{
			for (ComparissonTree.CompareNode u : compareTree.getLeaves())
			{
				InitializeCompareNode(key, arr, u, b);
			}

			// select the best node of the leaves
			chosenNode = compareTree.getLeaves().get(0);

			if (chosenNode.getTree() != null) 
			{
				topKTrees.add(chosenNode.getTree());
				compareTree.BuildLeaves(chosenNode);
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: InitializeCompareNode																			
	/** Description: Initialize the parameters of CompareNode for  AND type CompareNode							
	/*************************************************************************************************************/
	
	public void InitializeCompareNode (Atom key, Vector<List<DerivationTree>> arr, ComparissonTree.CompareNode v, Body body) 
	{
		boolean validTree = true;
		DerivationTree curTree = new DerivationTree();
		double weight = body.getRuleUsed().getWeight();
		
		for (int i = 0; i < arr.size(); i++) 
		{
			if (arr.get(i).size() > v.getIndices()[i])
			{
				weight *= arr.get(i).get( v.getIndices()[i] ).getWeight();
				curTree.addFactPointer(arr.get(i).get( v.getIndices()[i] ).getDerivedFact());
				curTree.addChild(arr.get(i).get( v.getIndices()[i] ));
				arr.get(i).get( v.getIndices()[i] ).addParent(curTree);
			}
			
			else // there is a child that does not participate in the tree
			{
				validTree = false;
				break;
			}
		}
		
		if (true == validTree) //confirm it is not empty tree
		{
			curTree.setWeight(weight);
			curTree.setDerivedFact(key);
			curTree.setBodyInProv(body);
			v.setTree(curTree);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: GetChildrenTreeVectors																					
	/** Description: Returns a vector of the derivation trees of all children of a circuit node														
	/*************************************************************************************************************/
	
	public void GetChildrenTreeVectors (Vector<List<DerivationTree>> arr, Body b)
	{
		for (Atom child : b.getAtoms()) 
		{
			arr.add(child.getTrees());
		}
    }
	
	
	
	/*************************************************************************************************************/
	/** Title: ChooseTopKTrees																					
	/** Description: Handles OR type node in the circuit														
	/*************************************************************************************************************/
	
	public void ChooseTopKTrees (List<DerivationTree> topKTrees)
	{
		Collections.sort(topKTrees); //sort in descending order
		if (topKTrees.size() > this.k) 
		{
			List<DerivationTree> sub = new Vector<DerivationTree>(topKTrees.subList(0, this.k));
			CleanProv(topKTrees);
			topKTrees.clear();
			topKTrees.addAll(sub);
		}
    }
	
	
	
	/*************************************************************************************************************/
	/** Title: CleanProv																					
	/** Description: 														
	/*************************************************************************************************************/
	
	public void CleanProv (List<DerivationTree> topKTrees)
	{
		if (true == this.online) 
		{
			List<DerivationTree> xor = new Vector<DerivationTree>(topKTrees.subList(this.k, topKTrees.size()));
			for (DerivationTree derivTree : xor) 
			{
				Provenance.getInstance().Get(derivTree.getDerivedFact()).remove(derivTree.getBodyInProv());
			}
		}
	}
	
	
	/*************************************************************************************************************/
	/** Title: MarkAtomsTreesChangedLastIteration																					
	/** Description: 									
	/*************************************************************************************************************/
	
	/*public void MarkAtomsTreesChangedLastIteration ()
	{
		for (Atom atom : this.treesUpdatedLastIter) 
		{
			atom.setTreesChangedLastIteration(true);
		}
	}*/
	
	
	
	/*************************************************************************************************************/
	/** Title: UpdateTreesWhileSemiNaive																					
	/** Description: Given an atom and a new derivation, Updates the Trees of this atom While Semi-Naive is running 									
	/*************************************************************************************************************/
	
	public void UpdateTreesWhileSemiNaive(Atom key, Body body)
	{
		List<DerivationTree> topKTrees = new Vector<DerivationTree>();
		if (true == body.AllAtomsHaveTopK()) 
		{
			GetTopKTreesForBody(key, body, topKTrees);
		}
		
		SiftTopKTrees(key, topKTrees);
	}
	
	

	/*************************************************************************************************************/
	/** Title: SiftTopKTrees																					
	/** Description:  									
	/*************************************************************************************************************/
	
	public void SiftTopKTrees (Atom key, List<DerivationTree> topKTrees)
	{
		if (false == topKTrees.isEmpty())
		{
			boolean relevant = true;
			if (null != key.getTrees() && true == key.getTrees().equals(topKTrees)) 
			{
				relevant = false;
			}
			
			if (null != key.getTrees()) 
			{
				topKTrees.addAll(key.getTrees());
			}
			
			ChooseTopKTrees(topKTrees);
			if (true == relevant && false == SameWeightTrees(topKTrees, key.getTrees()))
			{		
				key.setTopKUpdated(true);
				key.setTrees(topKTrees);
				this.treesUpdatedLastIter.add(key);
				key.setTreesChangedLastIteration(true);
			}
		}
	}
	
	
	/*************************************************************************************************************/
	/** Title: UpdateFactAtoms																					
	/** Description: Makes the trees for the fact atoms int the prov. 									
	/*************************************************************************************************************/
	
	public void SetTreeForFact (Atom fact) 
	{
		DerivationTree curTree = new DerivationTree();
		curTree.setWeight(1);
		curTree.setDerivedFact(fact);
		fact.AddTree(curTree);
		fact.setTopKUpdated(true);
		if (false == this.online) 
		{
			fact.setTreesChangedLastIteration(true);
		}
	}
}
